/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package pneuservis.backend.others;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import javax.inject.Inject;
import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresGuest;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.authz.annotation.RequiresUser;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author jajaja
 */
@Secured
@Interceptor
public class SecurityInterceptor {

//    private Subject subject;
//    private Logger log = LoggerFactory.getLogger(SecurityInterceptor.class);
//    
//    @AroundInvoke
//    public Object interceptGet(InvocationContext ctx) throws Exception {
//        log.info("Securing {}.{}({})", new Object[]{ctx.getClass().getName(),
//                    ctx.getMethod(), ctx.getParameters()});
//
//        final Class<? extends Object> runtimeClass = ctx.getTarget().getClass();
//
//        // Check if user is authenticated
//        boolean requiresAuthentication = false;
//        try { // check method first
//            ctx.getMethod().getAnnotation(RequiresAuthentication.class);
//            requiresAuthentication = true;
//        } catch (NullPointerException e) {
//            requiresAuthentication = false;
//        }
//
//        if (!requiresAuthentication) { // check class level
//            try {
//                runtimeClass.getAnnotation(RequiresAuthentication.class);
//                requiresAuthentication = true;
//            } catch (NullPointerException e) {
//                requiresAuthentication = false;
//            }
//        }
//
//        if (requiresAuthentication) {
//            log.debug("[security] checking for authenticated user.");
//            try {
//                if (!subject.isAuthenticated()) {
//                    throw new AuthorizationException();
//                }
//            } catch (Exception e) {
//                log.error("Access denied - {}: {}", e.getClass().getName(),
//                        e.getMessage());
//                throw e;
//            }
//        }
//        /**
//         * *********************************************************
//         */
//        // check if user has roles
//        boolean requiresRoles = false;
//        List<String> listOfRoles = null;
//
//        try { // check method first
//            RequiresRoles roles = ctx.getMethod().getAnnotation(
//                    RequiresRoles.class);
//            listOfRoles = Arrays.asList(roles.value());
//            requiresRoles = true;
//        } catch (NullPointerException e) {
//            requiresRoles = false;
//        }
//
//        if (!requiresRoles || listOfRoles == null) { // check class
//            try {
//                RequiresRoles roles = runtimeClass
//                        .getAnnotation(RequiresRoles.class);
//                listOfRoles = Arrays.asList(roles.value());
//                requiresRoles = true;
//            } catch (NullPointerException e) {
//                requiresRoles = false;
//            }
//        }
//
//        if (requiresRoles && listOfRoles != null) {
//            log.debug("[security] checking for roles.");
//            try {
//                boolean[] boolRoles = subject.hasRoles(listOfRoles);
//                boolean roleVerified = false;
//                for (boolean b : boolRoles) {
//                    if (b) {
//                        roleVerified = true;
//                        break;
//                    }
//                }
//                if (!roleVerified) {
//                    throw new AuthorizationException(
//                            "Access denied. User doesn't have enough privilege Roles:"
//                            + listOfRoles + " to access this page.");
//                }
//            } catch (Exception e) {
//                log.error("Access denied - {}: {}", e.getClass().getName(),
//                        e.getMessage());
//                throw e;
//            }
//        }
//
//        return ctx.proceed();
//    }
    
    private static final long serialVersionUID = 1L;

    @AroundInvoke
    public Object interceptShiroSecurity(InvocationContext context) throws Exception {
        Subject subject = SecurityUtils.getSubject();
        Class<?> c = context.getTarget().getClass();
        Method m = context.getMethod();

        if (!subject.isAuthenticated() && hasAnnotation(c, m, RequiresAuthentication.class)) {
            throw new UnauthenticatedException("Authentication required");
        }

        if (subject.getPrincipal() != null && hasAnnotation(c, m, RequiresGuest.class)) {
            throw new UnauthenticatedException("Guest required");
        }

        if (subject.getPrincipal() == null && hasAnnotation(c, m, RequiresUser.class)) {
            throw new UnauthenticatedException("User required");
        }

        RequiresRoles roles = getAnnotation(c, m, RequiresRoles.class);

        if (roles != null) {
            subject.checkRoles(Arrays.asList(roles.value()));
        }

        RequiresPermissions permissions = getAnnotation(c, m, RequiresPermissions.class);

        if (permissions != null) {
             subject.checkPermissions(permissions.value());
        }

        return context.proceed();
    }

    private static boolean hasAnnotation(Class<?> c, Method m, Class<? extends Annotation> a) {
        return m.isAnnotationPresent(a)
            || c.isAnnotationPresent(a)
            || c.getSuperclass().isAnnotationPresent(a);
    }

    private static <A extends Annotation> A getAnnotation(Class<?> c, Method m, Class<A> a) {
        return m.isAnnotationPresent(a) ? m.getAnnotation(a)
            : c.isAnnotationPresent(a) ? c.getAnnotation(a)
            : c.getSuperclass().getAnnotation(a);
    }
}